#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/3/26 12:01
# @Author  : 王凯
# @File    : geetest3.py
# @Project : scrapy_spider

import base64
import binascii
import hashlib
import io
import json
import os
import random
import time
from pathlib import Path

import execjs

import numpy as np
import oss2
import requests
import rsa
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from PIL import Image
from loguru import logger
from requests.exceptions import RequestException


class Predict:
    session = requests.session()
    session.headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
    }
    orc_url = "http://captcha.wkaanig.cn"
    show = False

    def __init__(self, show=False):
        self.show = show

    def word(self, image_io):
        result = self.session.post(
            f"{self.orc_url}/words", json={"image_string": base64.b64encode(image_io).decode()}, timeout=10
        ).json()
        pic_str = result["data"]
        points = [
            {"x": int(location.split(",")[0]), "y": int(location.split(",")[1])} for location in pic_str.split("|")
        ]
        return points

    def icon(self, image_io):
        result = self.session.post(
            f"{self.orc_url}/icon", json={"image_string": base64.b64encode(image_io).decode()}, timeout=10
        ).json()
        pic_str = result["data"]
        points = [
            {"x": int(location.split(",")[0]), "y": int(location.split(",")[1])} for location in pic_str.split("|")
        ]
        return points

    def space(self, image_io, sign=None):
        payload_data = {
            "model_name": "geetest_kj",
            "key": "ymmmmmmm",
            "content": sign,
            "image": base64.b64encode(image_io).decode(),
        }
        result = self.session.post("http://ocr.ymmmmmmm.com:19199/ocr", json=payload_data, timeout=10).json()
        pic_str = result["data"]
        points = [
            {"x": int(location.split(",")[0]), "y": int(location.split(",")[1])} for location in pic_str.split("|")
        ]
        return points

    def run_predict(self, pic_type, pic_url, sign=None):
        pic_content = self.session.get(pic_url, timeout=5).content
        if pic_type == "word":
            points = self.word(pic_content)
        elif pic_type == "icon":
            points = self.icon(pic_content)
        elif pic_type == "space":
            points = self.space(pic_content, sign)
        else:
            raise Exception(f"not support pic_type {pic_type}")
        if self.show:
            self.show_img(pic_content, points)
        return points

    @staticmethod
    def show_img(image_io, points):
        import matplotlib.pyplot as plt

        img = np.array(Image.open(io.BytesIO(image_io)))
        plt.imshow(img)
        for idx, box_ in enumerate(points, start=1):
            x1, y1 = box_["x"], box_["y"]
            plt.text(x1, y1, s=f"{idx}", color="white", verticalalignment="top", bbox={"color": "black", "pad": 0})
        plt.show()

    def upload_error(self, pic_url, pic_type):
        logger.error(f"upload error, pic_type {pic_type}, pic_url {pic_url}")
        file_name = pic_url.split("/")[-1]
        oss_client = oss2.Bucket(
            oss2.Auth("LTAI5tQjVgxe34iE8ZEqdMbB", "3eEIIxQ2eWKGxyz3lLrT2JZOf1Qs26"),
            "http://oss-cn-chengdu.aliyuncs.com",
            "wkaanig-img",
        )
        if pic_type in ["icon", "word"]:
            pass
            # oss_client.put_object(f"gt3/sample/{pic_type}/{file_name}", self.session.get(pic_url, timeout=5).content)
        else:
            logger.error(f"not support pic_type {pic_type}")


class EncryptUtils:
    location_list = [
        {"y": -58, "x": -157},
        {"y": -58, "x": -145},
        {"y": -58, "x": -265},
        {"y": -58, "x": -277},
        {"y": -58, "x": -181},
        {"y": -58, "x": -169},
        {"y": -58, "x": -241},
        {"y": -58, "x": -253},
        {"y": -58, "x": -109},
        {"y": -58, "x": -97},
        {"y": -58, "x": -289},
        {"y": -58, "x": -301},
        {"y": -58, "x": -85},
        {"y": -58, "x": -73},
        {"y": -58, "x": -25},
        {"y": -58, "x": -37},
        {"y": -58, "x": -13},
        {"y": -58, "x": -1},
        {"y": -58, "x": -121},
        {"y": -58, "x": -133},
        {"y": -58, "x": -61},
        {"y": -58, "x": -49},
        {"y": -58, "x": -217},
        {"y": -58, "x": -229},
        {"y": -58, "x": -205},
        {"y": -58, "x": -193},
        {"y": 0, "x": -145},
        {"y": 0, "x": -157},
        {"y": 0, "x": -277},
        {"y": 0, "x": -265},
        {"y": 0, "x": -169},
        {"y": 0, "x": -181},
        {"y": 0, "x": -253},
        {"y": 0, "x": -241},
        {"y": 0, "x": -97},
        {"y": 0, "x": -109},
        {"y": 0, "x": -301},
        {"y": 0, "x": -289},
        {"y": 0, "x": -73},
        {"y": 0, "x": -85},
        {"y": 0, "x": -37},
        {"y": 0, "x": -25},
        {"y": 0, "x": -1},
        {"y": 0, "x": -13},
        {"y": 0, "x": -133},
        {"y": 0, "x": -121},
        {"y": 0, "x": -49},
        {"y": 0, "x": -61},
        {"y": 0, "x": -229},
        {"y": 0, "x": -217},
        {"y": 0, "x": -193},
        {"y": 0, "x": -205},
    ]

    def __init__(self):
        self.public_key = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
        self.public_key_mode = "10001"
        self.sec_key = self.create_secret_key(8)

    @staticmethod
    def bytes_to_string(enc_byte):
        flag = 3
        I9z = 3
        t6B = {"wa": 7274496, "xa": 9483264, "ya": 19220, "za": 235, "r": "."}
        string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()"

        def da(r0B):
            if r0B < 0 or r0B >= len(string):
                return "."
            else:
                return string[r0B]

        def function(Q6B, x6B):
            a5r = 0
            v6B = 0
            while a5r != -1:
                if a5r == 1:
                    a5r = 2 if v6B >= 0 else 4
                elif a5r == 0:
                    I6B = 0
                    v6B = 24 - 1  # t6B["Aa"]
                    a5r = 1
                elif a5r == 2:
                    if 1 == (x6B >> v6B & 1):
                        I6B = (I6B << 1) + (Q6B >> v6B & 1)
                    a5r = 3
                elif a5r == 4:
                    return I6B
                elif a5r == 3:
                    v6B -= 1
                    a5r = 1

        N6B = function
        j6B = ""
        K6B = ""
        c6B = len(enc_byte)
        f6B = 0

        while flag != -1:
            if flag == 6:
                # 每三位为一组  246 76 179
                B6B = (enc_byte[f6B] << 16) + (enc_byte[f6B + 1] << 8) + enc_byte[f6B + 2]
                j6B += (
                        da(N6B(B6B, t6B["wa"]))
                        + da(N6B(B6B, t6B["xa"]))
                        + da(N6B(B6B, t6B["ya"]))
                        + da(N6B(B6B, t6B["za"]))
                )
                flag = 8
            elif flag == 5:
                flag = 6 if f6B + 2 < c6B else 9
            elif flag == 3:
                flag = 5 if f6B < c6B else 11
            elif flag == 8:
                f6B += 3
                flag = 3
            elif flag == 9:
                n6B = c6B % 3
                flag = 10
            elif flag == 10:
                if 2 == n6B:
                    B6B = (enc_byte[f6B] << 16) + (enc_byte[f6B + 1] << 8)
                    j6B += da(N6B(B6B, t6B["wa"])) + da(N6B(B6B, t6B["xa"])) + da(N6B(B6B, t6B["ya"]))
                    K6B = t6B["r"]
                else:
                    if 1 == n6B:
                        B6B = enc_byte[f6B] << 16
                        j6B += da(N6B(B6B, t6B["wa"])) + da(N6B(B6B, t6B["xa"]))
                        K6B = t6B["r"] + t6B["r"]
                flag = 8
            elif flag == 11:
                return j6B + K6B

    def encrypt_w(self, input_data: dict, ajax=False):
        text = json.dumps(input_data, separators=(",", ":"))
        # aes 对称加密  进行轨迹加密
        iv = b"0000000000000000"
        ciphertext = list(self.aes_encrypt(text, self.sec_key.decode("utf-8"), iv))
        enc_text = self.bytes_to_string(ciphertext)
        # rsa 不对称性对 aes的密钥进行加密
        if ajax:
            w = enc_text
        else:
            enc_sec_key = self.rsa_encrypt(self.sec_key, self.public_key, self.public_key_mode)
            w = enc_text + enc_sec_key
        return w

    def aes_encrypt(self, text, key, iv, style="pkcs7"):
        """
        :param text: 文本
        :param key: 密钥
        :param iv: 初始iv  bytes
        :param style: 返回函数类型
        :return:
        """
        encryptor = AES.new(key.encode("utf-8"), AES.MODE_CBC, iv)
        pad_pkcs7 = pad(text.encode("utf-8"), AES.block_size, style=style)
        return encryptor.encrypt(pad_pkcs7)

    def rsa_encrypt(self, text: bytes, n=None, e=None):
        """
        对text 进行rsa加密
        """
        if n is None:
            n = self.public_key
        if e is None:
            e = self.public_key_mode

        public_key = rsa.PublicKey(int(n, 16), int(e, 16))  # rsa库公钥形式
        rs = rsa.encrypt(text, public_key)
        return rs.hex()

    @staticmethod
    def create_secret_key(size):
        # 作用是返回的二进制数据的十六进制表示。每一个字节的数据转换成相应的2位十六进制表示
        return binascii.hexlify(os.urandom(size))

    def get_slide_track(self, distance):
        """
        根据滑动距离生成滑动轨迹
        :param distance: 需要滑动的距离
        :return: 滑动轨迹<type 'list'>: [[x,y,t], ...]
            x: 已滑动的横向距离
            y: 已滑动的纵向距离, 除起点外, 均为0
            t: 滑动过程消耗的时间, 单位: 毫秒
        """

        if not isinstance(distance, int) or distance < 0:
            raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
        # 初始化轨迹列表
        slide_track = [
            [random.randint(-50, -10), random.randint(-50, -10), 0],
            [0, 0, 0],
        ]
        # 共记录count次滑块位置信息
        count = 40 + int(distance / 2)
        # 初始化滑动时间
        t = random.randint(50, 100)
        # 记录上一次滑动的距离
        _x = 0
        _y = 0
        for i in range(count):
            # 已滑动的横向距离
            x = round(self.__ease_out_expo(i / count) * distance)
            # y = round(__ease_out_expo(i / count) * 14)
            # 滑动过程消耗的时间
            t += random.randint(10, 50)
            if x == _x:
                continue
            slide_track.append([x, _y, t])
            _x = x
        slide_track.append(slide_track[-1])
        return slide_track

    @staticmethod
    def __ease_out_expo(sep):
        """
        轨迹相关操作
        """
        if sep == 1:
            return 1
        else:
            return 1 - pow(2, -10 * sep)

    @staticmethod
    def get_c_s_encry(res_c, res_s, user_route_encry_key=None):
        if user_route_encry_key is None:
            user_route_encry_key = "M(*((1((M(("  # 用户移动轨迹 设成[] 则生成 M(*((1((M((
        if not (res_c and res_s):
            return user_route_encry_key
        n = 0
        i = 2
        o = user_route_encry_key
        a, _, c = res_c[0], res_c[2], res_c[4]

        while True:
            s = res_s[n: n + i]
            if not s:
                break
            li = int(s, 16)
            u = chr(li)
            f = (a * li * li + _ * li + c) % len(user_route_encry_key)
            o = o[:f] + u + o[f:]
            n += i

        return o

    def merge_slide_image(self, file_io):
        im = Image.open(file_io)
        width, height = im.size

        new_im = Image.new("RGB", (260, height))
        im_list_upper = []
        im_list_lower = []

        for location in self.location_list:
            if location["y"] == -58:
                im_list_upper.append(im.crop((abs(location["x"]), height // 2, abs(location["x"]) + 10, height)))
            if location["y"] == 0:
                im_list_lower.append(im.crop((abs(location["x"]), 0, abs(location["x"]) + 10, height // 2)))

        x_offset = 0
        for img in im_list_upper:
            new_im.paste(img, (x_offset, 0))
            x_offset += img.size[0]

        x_offset = 0
        for img in im_list_lower:
            new_im.paste(img, (x_offset, height // 2))
            x_offset += img.size[0]
        return new_im

    @staticmethod
    def is_px_equal(img1, img2, x, y):
        """
        判断两个像素是否相同
        :param img1: 图片1
        :param img2:图片2
        :param x:位置1
        :param y:位置2
        :return:像素是否相同
        """
        pix1 = img1.load()[x, y]
        pix2 = img2.load()[x, y]
        threshold = 60

        if (
                abs(pix1[0] - pix2[0]) < threshold
                and abs(pix1[1] - pix2[1]) < threshold
                and abs(pix1[2] - pix2[2]) < threshold
        ):
            return True
        else:
            return False

    def get_distance(self, bg_str_or_io: bytes, full_bg_str_or_io: bytes):
        img1 = self.merge_slide_image(io.BytesIO(bg_str_or_io))
        img2 = self.merge_slide_image(io.BytesIO(full_bg_str_or_io))
        left = 0
        for i in range(left, img1.size[0]):
            for j in range(img1.size[1]):
                if not self.is_px_equal(img1, img2, i, j):
                    left = i
                    return left
        return left

    @staticmethod
    def encrypt_pass_way(pass_way):
        def e(e):
            t = []
            r = 0
            n, i, o = 0, 0, 0
            for a in range(len(e) - 1):
                n = round(e[a + 1][0] - e[a][0])
                i = round(e[a + 1][1] - e[a][1])
                o = round(e[a + 1][2] - e[a][2])
                if n == 0 and i == 0 and o == 0:
                    continue
                if n == 0 and i == 0:
                    r += o
                else:
                    t.append([n, i, o + r])
                    r = 0
            if r != 0:
                t.append([n, i, r])
            return t

        def r(e):
            t = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"
            r = len(t)
            n = ""
            i = abs(e)
            o = i // r
            if o >= r:
                o = r - 1
            if o:
                n = t[o]
            i = i % r
            a = ""
            if e < 0:
                a += "!"
            if n:
                a += "$"
            return a + n + t[i]

        def n(e):
            t = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]]
            r = "stuvwxyz~"
            for i in range(len(t)):
                if e[0] == t[i][0] and e[1] == t[i][1]:
                    return r[i]
            return 0

        t = e(pass_way)
        i, o, a = [], [], []
        for e in t:
            t = n(e)
            if not t:
                i.append(r(e[0]))
                o.append(r(e[1]))
            else:
                o.append(t)
            a.append(r(e[2]))
        return "".join(i) + "!!" + "".join(o) + "!!" + "".join(a)

    @staticmethod
    def encrypt_user_response(e, t):
        r = t[-2:]
        n = []

        for i in range(len(r)):
            o = ord(r[i])
            n.append(o - 87 if o > 57 else o - 48)

        r = n[0] * 36 + n[1]
        a = round(e) + r
        t = t[:-2]
        s = [[], [], [], [], []]
        u = {}
        c = 0
        i = 0
        f = len(t)

        while i < f:
            _ = t[i]

            if _ not in u:
                u[_] = 1
                s[c].append(_)
                c += 1
                c = 0 if c == 5 else c

            i += 1

        li = a
        h = 0
        v = 4
        d = ""
        p = [1, 2, 5, 10, 50]

        while li > 0:
            if li - p[v] >= 0:
                h = int(random.random() * len(s[v]))
                d += s[v][h]
                li -= p[v]
            else:
                s.pop(v)
                p.pop(v)
                v -= 1

        return d


class Geetest3(EncryptUtils, Predict):
    SESSION_URL_BASE = "https://api.geevisit.com"
    GT_AND_CHALLENGE_URLS = {
        "slide": "https://www.geetest.com/demo/gt/register-slide-official",
        "click": "https://www.geetest.com/demo/gt/register-click-official",
        "icon": "https://www.geetest.com/demo/gt/register-icon",
        "space": "https://www.geetest.com/api/user/show/register-space",
    }

    def __init__(self, debug=False, session=requests.Session(), timeout=10):
        super().__init__()
        self.debug = debug
        self.timeout = timeout
        self.static_url = "https://static.geetest.com"
        self.session = session
        if "python" in self.session.headers.get("User-Agent"):
            self.session.headers = {
                "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
            }

    def _gct(self, gct_url, obj):
        js_code = (
                "exports = undefined;module = undefined;"
                + self.cache_js(js_url=gct_url)
                + "function get_gct(obj){_gct(obj);return obj}"
        )
        try:
            return execjs.compile(js_code).call("get_gct", obj)
        except Exception as e:
            logger.error(f"Failed to execute JavaScript code: {e}")
            return None

    def cache_js(self, js_url):
        cache_js = Path(f"/tmp/{js_url.split('/')[-1]}")
        if cache_js.exists():
            try:
                with open(cache_js, "r") as f:
                    return f.read()
            except IOError as e:
                logger.error(f"Failed to read cached JavaScript file: {e}")
                return None
        else:
            try:
                cache_js.parent.mkdir(parents=True, exist_ok=True)
                file_io = self.session.get(js_url).content.decode("utf-8")
                with open(cache_js, "w") as f:
                    f.write(file_io)
            except (RequestException, IOError) as e:
                logger.error(f"Failed to cache JavaScript file: {e}")
                return None
        return file_io

    def get_gt_and_challenge(self, verify="slide"):
        url = self.GT_AND_CHALLENGE_URLS.get(verify, self.GT_AND_CHALLENGE_URLS["slide"])
        params = {"t": f"{int(time.time() * 1000)}"}
        try:
            response = self.session.get(url, params=params, timeout=self.timeout)
            response.raise_for_status()  # 引发HTTPError用于错误的响应状态
            resp = response.json()
        except RequestException as e:
            logger.error(f"Failed to get GT and challenge: {e}")
            return None, None
        return resp["gt"], resp["challenge"]

    def first(self, gt, challenge=None):
        url = "https://api.geetest.com/gettype.php"
        params = {"gt": gt, "callback": f"geetest_{int(time.time() * 1000)}"}
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        return result["data"]

    def second(self, gt, challenge, first_result=None):
        if first_result is None:
            first_result = {
                "type": "fullpage",
                "static_servers": ["static.geetest.com/", "static.geevisit.com/"],
                "beeline": "/static/js/beeline.1.0.1.js",
                "voice": "/static/js/voice.1.2.4.js",
                "click": "/static/js/click.3.1.0.js",
                "fullpage": "/static/js/fullpage.9.1.9-r8k4eq.js",
                "slide": "/static/js/slide.7.9.2.js",
                "geetest": "/static/js/geetest.6.0.9.js",
                "aspect_radio": {"slide": 103, "click": 128, "voice": 128, "beeline": 50},
            }
        url = "https://api.geetest.com/get.php"
        input_data = {
            "gt": gt,
            "challenge": challenge,
            "offline": False,
            "new_captcha": True,
            "product": "embed",
            "width": "300px",
            "https": True,
            "protocol": "https://",
            "cc": 20,
            "ww": True,
            "i": "-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1",
        }
        input_data.update(first_result)
        params = {
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "pt": "0",
            "client_type": "web",
            "w": self.encrypt_w(input_data),
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        return result["data"]["c"], result["data"]["s"]

    def third(self, gt, challenge, res_c, res_s):
        pass_time = random.randint(500, 2000)
        input_data = {
            "lang": "zh-cn",
            "type": "fullpage",
            "tt": self.get_c_s_encry(res_c=res_c, res_s=res_s),
            "light": "DIV_0",
            "s": "c7c3e21112fe4f741921cb3e4ff9f7cb",
            "h": "321f9af1e098233dbd03f250fd2b5e21",
            "hh": "39bd9cad9e425c3a8f51610fd506e3b3",
            "hi": "09eb21b3ae9542a9bc1e8b63b3d9a467",
            "vip_order": -1,
            "ct": -1,
            "ep": {
                "v": "9.1.9-r8k4eq",
                "te": False,
                "$_BBp": True,
                "ven": "Google Inc. (Intel)",
                "ren": "ANGLE (Intel, Intel(R) UHD Graphics 770 (0x00004680) Direct3D11 vs_5_0 ps_5_0, D3D11)",
                "fp": [
                    "move",
                    random.randint(500, 1000),
                    random.randint(50, 300),
                    int(time.time() * 1000) - 5000,
                    "pointermove",
                ],
                "lp": [
                    "up",
                    random.randint(500, 1000),
                    random.randint(50, 300),
                    int(time.time() * 1000) - 4320,
                    "pointerup",
                ],
                "em": {"ph": 0, "cp": 0, "ek": "11", "wd": 1, "nt": 0, "si": 0, "sc": 0},
                "tm": {
                    "a": int(time.time() * 1000) - 4000,
                    "b": int(time.time() * 1000) - 4000 + 5,
                    "c": int(time.time() * 1000) - 4000 + 10,
                    "d": 0,
                    "e": 0,
                    "f": int(time.time() * 1000) - 4000 + 2,
                    "g": int(time.time() * 1000) - 4000 + 2,
                    "h": int(time.time() * 1000) - 4000 + 2,
                    "i": int(time.time() * 1000) - 4000 + 2,
                    "j": int(time.time() * 1000) - 4000 + 2,
                    "k": 0,
                    "l": int(time.time() * 1000) - 2468,
                    "m": int(time.time() * 1000) - 2366,
                    "n": int(time.time() * 1000) - 2299,
                    "o": int(time.time() * 1000) - 2099,
                    "p": int(time.time() * 1000) - 2087,
                    "q": int(time.time() * 1000) - 2087,
                    "r": int(time.time() * 1000) - 2087,
                    "s": int(time.time() * 1000) - 2000,
                    "t": int(time.time() * 1000) - 2000,
                    "u": int(time.time() * 1000) - 2000,
                },
                "dnf": "dnf",
                "by": 0,
            },
            "passtime": pass_time,
            "rp": hashlib.md5(f"{gt}{challenge}{pass_time}".encode()).hexdigest(),
            "captcha_token": "1265749785",  # 代码hash
            "otpj": "jm4jwcx7",  # 代码中的值
        }
        url = "https://api.geevisit.com/ajax.php"
        params = {
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "pt": "0",
            "client_type": "web",
            "w": self.encrypt_w(input_data, ajax=True),
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        return result["data"]["result"]

    def forth(self, verify_type, gt, challenge):
        url = "https://api.geevisit.com/get.php"
        params = {
            "is_next": "true",
            "type": verify_type,
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "https": "true",
            "protocol": "https://",
            "offline": "false",
            "product": "float",
            "api_server": "api.geevisit.com",
            "isPC": "true",
            "autoReset": "true",
            "width": "100%",
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        if "data" in result:
            data = result["data"]
        else:
            data = result
        pic = data.get("pic")
        gct_path = data["gct_path"]
        c = data["c"]
        s = data["s"]
        pic_type = data.get("pic_type")
        sign = data.get("sign")
        full_bg_url = data.get("fullbg")
        bg_url = data.get("bg")
        slice_url = data.get("slice")
        new_challenge = data.get("challenge")
        return pic, gct_path, pic_type, c, s, sign, full_bg_url, bg_url, slice_url, new_challenge

    def refresh(self, verify_type, gt, challenge):
        url = "https://api.geevisit.com/refresh.php"
        params = {
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "type": verify_type,
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        if "data" in result:
            data = result["data"]
        else:
            data = result
        pic = data.get("pic")
        sign = data.get("sign")
        bg_url = data.get("bg")
        full_bg_url = data.get("fullbg")
        new_challenge = data.get("challenge")
        return pic, sign, bg_url, full_bg_url, new_challenge

    def submit(self, gt, challenge, res_c, res_s, image_path, gct_path, pic_type, sign=None):
        # points = [{"x": 172, "y": 101}, {"x": 172, "y": 102}]
        pic_mask_width = 340
        pic_mask_height = 340
        points = self.run_predict(pic_type=pic_type, pic_url=self.static_url + image_path, sign=sign)
        points_string = ",".join(
            [
                f"{round(item['x'] / pic_mask_width * 100 * 100)}_{round(item['y'] / pic_mask_height * 100 * 100)}"
                for item in points
            ]
        )
        pass_time = random.randint(500, 5000)
        input_data = {
            "lang": "zh-cn",
            "passtime": pass_time,
            "a": points_string,
            "pic": image_path,
            "tt": self.get_c_s_encry(res_c=res_c, res_s=res_s),
            "ep": {
                "ca": [
                    {
                        "x": random.randint(500, 1000),
                        "y": random.randint(500, 1000),
                        "t": 0,
                        "dt": random.randint(500, 1000),
                    },
                    {
                        "x": random.randint(500, 1000),
                        "y": random.randint(500, 1000),
                        "t": 0,
                        "dt": random.randint(500, 1000),
                    },
                    {
                        "x": random.randint(500, 1000),
                        "y": random.randint(500, 1000),
                        "t": 0,
                        "dt": random.randint(500, 1000),
                    },
                ],
                "v": "3.1.0",
                "$_FB": False,
                "me": True,
                "tm": {
                    "a": int(time.time() * 1000) - 6000,
                    "b": int(time.time() * 1000) - 6000 + 5,
                    "c": int(time.time() * 1000) - 6000 + 10,
                    "d": 0,
                    "e": 0,
                    "f": int(time.time() * 1000) - 6000 + 2,
                    "g": int(time.time() * 1000) - 6000 + 2,
                    "h": int(time.time() * 1000) - 6000 + 2,
                    "i": int(time.time() * 1000) - 6000 + 2,
                    "j": int(time.time() * 1000) - 6000 + 2,
                    "k": 0,
                    "l": int(time.time() * 1000) - 3468,
                    "m": int(time.time() * 1000) - 3366,
                    "n": int(time.time() * 1000) - 3299,
                    "o": int(time.time() * 1000) - 3099,
                    "p": int(time.time() * 1000) - 3087,
                    "q": int(time.time() * 1000) - 3087,
                    "r": int(time.time() * 1000) - 3087,
                    "s": int(time.time() * 1000) - 3000,
                    "t": int(time.time() * 1000) - 3000,
                    "u": int(time.time() * 1000) - 3000,
                },
            },
            "h9s9": "1816378497",
            "rp": hashlib.md5(f"{gt}{challenge}{pass_time}".encode()).hexdigest(),
        }
        # input_data.update(self._gct(self.static_url + gct_path, gct_input))
        url = "https://api.geevisit.com/ajax.php"
        if pic_type == "space":
            time.sleep(2)
        else:
            time.sleep(1)
        params = {
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "pt": "0",
            "client_type": "web",
            "w": self.encrypt_w(input_data),
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        return result

    def run_click(self, gt, challenge, res_c, res_s, verify_type, image_path, gct_path, pic_type, sign=None):
        verify_result = self.submit(
            gt, challenge, res_c, res_s, image_path=image_path, gct_path=gct_path, pic_type=pic_type, sign=sign
        )
        if verify_result.get("data", {}).get("result") == "success":
            return verify_result.get("data", {}).get("validate")
        else:
            logger.error(verify_result)
            self.upload_error(pic_url=f"{self.static_url}{image_path}", pic_type=pic_type)
            for _ in range(4):
                pic, sign, bg_url, full_bg_url, new_challenge = self.refresh(verify_type, gt, challenge)
                verify_result = self.submit(
                    gt, challenge, res_c, res_s, image_path=pic, gct_path=gct_path, pic_type=pic_type, sign=sign
                )
                if verify_result.get("data", {}).get("result") == "success":
                    return verify_result.get("data", {}).get("validate")
                else:
                    logger.error(verify_result)
                    self.upload_error(pic_url=f"{self.static_url}{pic}", pic_type=pic_type)

    def submit_slide(self, gt, challenge, res_c, res_s, pass_way):
        last_point_x, _, last_point_time = pass_way[-1]
        url = "https://api.geevisit.com/ajax.php"
        pass_time = last_point_time
        input_data = {
            "lang": "zh-cn",
            "userresponse": self.encrypt_user_response(last_point_x, challenge),
            "passtime": pass_time,
            "imgload": random.randint(50, 100),
            "aa": self.get_c_s_encry(res_c, res_s, self.encrypt_pass_way(pass_way)),
            "ep": {
                "v": "7.9.2",
                "$_BIE": False,
                "me": True,
                "tm": {
                    "a": int(time.time() * 1000) - 2000,
                    "b": int(time.time() * 1000) - 2000 + 5,
                    "c": int(time.time() * 1000) - 2000 + 10,
                    "d": 0,
                    "e": 0,
                    "f": int(time.time() * 1000) - 2000 + 2,
                    "g": int(time.time() * 1000) - 2000 + 2,
                    "h": int(time.time() * 1000) - 2000 + 2,
                    "i": int(time.time() * 1000) - 2000 + 2,
                    "j": int(time.time() * 1000) - 2000 + 2,
                    "k": 0,
                    "l": int(time.time() * 1000) - 2468,
                    "m": int(time.time() * 1000) - 2366,
                    "n": int(time.time() * 1000) - 2299,
                    "o": int(time.time() * 1000) - 2099,
                    "p": int(time.time() * 1000) - 2087,
                    "q": int(time.time() * 1000) - 2087,
                    "r": int(time.time() * 1000) - 2087,
                    "s": int(time.time() * 1000) - 2000,
                    "t": int(time.time() * 1000) - 2000,
                    "u": int(time.time() * 1000) - 2000,
                },
                "td": -1,
            },
            "h9s9": "1803797734",
            "rp": hashlib.md5(f"{gt}{challenge}{pass_time}".encode()).hexdigest(),
        }
        time.sleep(1)
        params = {
            "gt": gt,
            "challenge": challenge,
            "lang": "zh-cn",
            "$_BCN": "0",
            "client_type": "web",
            "w": self.encrypt_w(input_data),
            "callback": f"geetest_{int(time.time() * 1000)}",
        }
        response = self.session.get(url, params=params, timeout=self.timeout)
        resp = response.text
        result = json.loads(resp[resp.find("{"): resp.rfind("}") + 1])
        if self.debug:
            logger.debug(result)
        return result

    def run_slide(self, gt, challenge, new_challenge, res_c, res_s, full_bg_url, bg_url, slice_url=None):
        distance = self.get_distance(
            self.session.get(self.static_url + "/" + full_bg_url, timeout=self.timeout).content,
            self.session.get(self.static_url + "/" + bg_url, timeout=self.timeout).content,
        )
        verify_result = self.submit_slide(gt, new_challenge, res_c, res_s, self.get_slide_track(distance - 7))
        if verify_result.get("message") == "success":
            return verify_result.get("validate")
        elif verify_result.get("message") == "fail":
            logger.error(verify_result)
            verify_type = "multilink"
            for _ in range(4):
                pic, sign, bg_url, full_bg_url, new_challenge = self.refresh(verify_type, gt, new_challenge)
                distance = self.get_distance(
                    self.session.get(self.static_url + "/" + full_bg_url, timeout=self.timeout).content,
                    self.session.get(self.static_url + "/" + bg_url, timeout=self.timeout).content,
                )
                verify_result = self.submit_slide(gt, new_challenge, res_c, res_s, self.get_slide_track(distance - 7))
                if verify_result.get("message") == "success":
                    return verify_result.get("validate")
                else:
                    logger.error(verify_result)
        else:
            logger.error(verify_result)

    def run(self, gt, challenge, proxies=None):
        if proxies:
            self.session.proxies = proxies
        logger.info(f"Geetest3 is running [gt: {gt}, challenge: {challenge}]")
        first_result = self.first(gt, challenge)
        res_c, res_s = self.second(gt, challenge, first_result=first_result)
        verify_type = self.third(gt, challenge, res_c, res_s)
        if verify_type == "slide":
            pic, gct_path, pic_type, res_c, res_s, sign, full_bg, bg, slice_url, new_challenge = self.forth(
                "slide3", gt, challenge
            )
            logger.info(f"Geetest3 is predicted [verify_type: {verify_type}]")
            result = self.run_slide(gt, challenge, new_challenge, res_c, res_s, full_bg, bg, slice_url=slice_url)
        else:
            pic, gct_path, pic_type, res_c, res_s, sign, full_bg, bg, slice_url, new_challenge = self.forth(
                verify_type, gt, challenge
            )
            logger.info(f"Geetest3 is predicted [verify_type: {pic_type}]")
            result = self.run_click(gt, challenge, res_c, res_s, verify_type, pic, gct_path, pic_type, sign)
        if not result:
            raise Exception("Geetest3 is failed")
        return_data = {"validate": result, "challenge": new_challenge or challenge}
        logger.info(f"Geetest3 is finished [{return_data}]")
        return return_data

    def test(self):
        verify_type_list = ["slide", "click", "icon", "space"]
        for verify_type in verify_type_list:
            gt, challenge = self.get_gt_and_challenge(verify_type)
            try:
                result = self.run(gt, challenge)
            except Exception as e:
                logger.error(f"Error during test run: {e}")


if __name__ == "__main__":
    Geetest3().test()
